/**
 * 缓冲分析
 * @example
 *  let fb = new Buffer(viewer, {
 *    type: 'Polygon',
 *    position: [[108.339641, 25.555156], [108.37527, 25.557853], [108.379809, 25.543639]],
 *    radius: 1000,
 *    steps: 64,
 *    color: Freedo.Color.RED,
 *    bufferColor: Freedo.Color.CYAN.withAlpha(0.5),
 *    editDisabled: true
 *  });
 * @class
 */
 class Buffer {
    /**
     * @constructor
     * @param {Viewer} viewer 
     * @param {Object} option 配置参数
     * @param {String} option.type 类型，'Point'、'Polyline'或'Polygon'
     * @param {String} [option.id=GUID] 唯一id值
     * @param {Array} [option.position] 点或点组，格式：[lon, lat, height] 或 [[lon, lat, height] , [lon, lat, height], ...]。不设置时激活点选功能
     * @param {Array} [option.radius=1000] 绘制缓冲区的距离，单位：米
     * @param {Array} [option.steps=64] 步数
     * @param {String} [option.color=new Freedo.Color(1.0, 1.0, 0.0, 1.0)] 点或多边形的颜色
     * @param {String} [option.outlineColor=new Freedo.Color(1.0, 0.5, 0.0, 1.0)] 点边框颜色
     * @param {Number} [option.pixelSize=12.0] 点的大小
     * @param {Number} [option.outlineWidth=2.0] 点边框宽度
     * @param {String} [option.lineColor=new Freedo.Color(1.0, 1.0, 0.0, 1.0)] 折线颜色
     * @param {Number} [option.lineWidth=4.0] 折线宽度
     * @param {String} [option.bufferColor=new Freedo.Color(1.0, 0.0, 0.0, 0.5)] 缓冲区的颜色
     * @param {Boolean} [option.editDisabled=false] 不允许编辑
     * @param {Boolean} [option.show=true] 显示
     */
    constructor(viewer, option = {}) {
      if (!turf) {
        throw new ReferenceError('turf 未定义');
      }
      this._viewer = viewer;
      this._data = option;
  
      this._id = Freedo.defaultValue(option.id, Freedo.createGuid());
      this._show = Freedo.defaultValue(option.show, true);
  
      this._primitive = undefined;
      this._bufferPrimitive = undefined;
  
      this._handler = undefined;
      this._event = new Freedo.Event();
      this._eventHelper = new Freedo.EventHelper();
      
      // 编辑相关
      this._updatePosition = undefined;
      this._editPointEntities = undefined;
      this._startCapture = false;
  
      this._actionType = undefined;
      this._actionIndex = undefined;
      this.ACTION_TYPE = {
        PAN: 0, // 平移点
        VERTEX: 1, // 顶点
        MIDDLE: 2 // 中间点
      };
  
      this._startCartesian = undefined;
      this._endCartesian = undefined;
  
      this.create();
    }
  
    get option() {
      return this.getData();
    }
  
    get startCapture() {
      return this._startCapture;
    }
  
    setData(option = {}) {
      for(let key in option) {
        this._data[key] = option[key];
      }
      this.build();
    }
  
    create() {
      let option = this._data;
  
      if (!option.type) {
        throw new ReferenceError('type 未定义');
      }
  
      if (option.position) {
        this.build();
      } else {
        this.addLsnr();
      }
    }
  
    addLsnr() {
      if (this._data.type === 'Polygon') {
        let pge = new Freedo.FdMicroApp.FdPolygonEditor(this._viewer);
  
        pge.on((et, ev) => {
          if (et === 'DataChanged') {
            if (ev && ev.polygon && ev.polygon.ptsG) {
              this._data.position = ev.polygon.ptsG;
  
              pge.cancel();
              pge.removeAll();
  
              this.build();
            }
          }
        });
  
        pge.run();
        pge.showMouseTip(true);
        pge.setAutoDel(true);
  
      } else {
        let ple = new Freedo.FdMicroApp.FdPolylineEditor(this._viewer);
  
        ple.on((et, ev) => {
          if (this._data.type === 'Point' && et === 'PointAdded') {
            if (ev && ev.position) {
              this._data.position = ev.position;
              ple.cancel();
              ple.removeAll();
  
              this.build();
            }
          } else if (this._data.type === 'Polyline' && et === 'DataChanged') {
            if (ev && ev.polyline && ev.polyline.ptsG) {
              this._data.position = ev.polyline.ptsG;
              ple.cancel();
              ple.removeAll();
  
              this.build();
            }
          }
        });
  
        ple.run();
        ple.showMouseTip(true);
        ple.setAutoClose(false);
        ple.setAutoDelLines(true);
      }
    }
  
    registerHandlers() {
      this._handler = this._handler && this._handler.destroy();
      this._handler = new Freedo.ScreenSpaceEventHandler(this._viewer.scene.canvas);
  
      this._handler.setInputAction(this.onLeftClick.bind(this), Freedo.ScreenSpaceEventType.LEFT_CLICK);
      this._handler.setInputAction(this.onRightClick.bind(this), Freedo.ScreenSpaceEventType.RIGHT_CLICK);
      this._handler.setInputAction(this.onMouseMove.bind(this), Freedo.ScreenSpaceEventType.MOUSE_MOVE);
      this._handler.setInputAction(this.onLeftDown.bind(this), Freedo.ScreenSpaceEventType.LEFT_DOWN);
      this._handler.setInputAction(this.onLeftUp.bind(this), Freedo.ScreenSpaceEventType.LEFT_UP);
    }
  
    /**
     * 注册事件捕捉
     * @param {Buffer~BufferCallback} listener 当有事件触发时被执行的函数。
     * @param {Object} [scope] listener函数执行时的绑定的对象。
     * @returns {Freedo.Event~RemoveCallback} 返回一个函数，调用该函数可以取消监听。
     */
    on (listener, scope) {
      return this._eventHelper.add(this._event, listener, scope);
    }
    /**
    * @callback Buffer~BufferCallback
    * @param {String} eventType 事件类型有：DataChanged
    * @param {Object} eventArg DataChanged事件对应的参数
    */
  
    /**
     * 显示
     */
    show() {
      this.setShowEntity(true);
      this.setShowBuffer(true);
      this._startCapture && this.setShowEdit(true);
  
      this._show = true;
      this._viewer.scene.requestRender();
    }
  
    /**
     * 隐藏
     */
    hide() {
      this.setShowEntity(false);
      this.setShowBuffer(false);
      this._startCapture && this.setShowEdit(false);
  
      this._show = false;
      this._viewer.scene.requestRender();
    }
  
    build() {
      if (!this._data.position) {
        return;
      }
  
      let position = Freedo.clone(this._data.position, true);
  
      this.update(position);
  
      if (this._data.type === 'Point') {
        let point = this.cartesianFromDegreeArray(position);
  
        this._primitive = this.createPoint(point);
        this._viewer.scene.primitives.add(this._primitive);
      }
  
      !Freedo.defaultValue(this._data.editDisabled, false) && this.startEdit();
      !this._show && this.hide();
      !this._handler && this.registerHandlers();
  
      this._viewer.scene.requestRender();
      this._event.raiseEvent('DataChanged', this);
    }
  
    update(position) {
      if (!position) {
        return;
      }
  
      this.removeAll();
  
      let feature = undefined;
      let primitive = undefined;
  
      if (this._data.type === 'Point') {
  
        feature = turf.point(position);
        
        // update时不更新point
  
        // let point = this.cartesianFromDegreeArray(position);
        // this._primitive = this.createPoint(point);
  
      } else if (this._data.type === 'Polyline') {
  
        feature = turf.lineString(position);
  
        let linePoints = position.map(item => {
          return this.cartesianFromDegreeArray(item);
        });
  
        primitive = this.createPolyline(linePoints);
  
      } else if (this._data.type === 'Polygon') {
  
        position.push(position[0]);
  
        feature = turf.polygon([position]);
  
        let polygonPoints = position.map(item => {
          return this.cartesianFromDegreeArray(item);
        });
  
        primitive = this.createPolygon(polygonPoints);
      }
  
      let radius = Freedo.defaultValue(this._data.radius, 1000) / 1000;
      let steps = Freedo.defaultValue(this._data.steps, 64);
      let buffer = turf.buffer(feature, radius, {
        units: 'kilometers',
        steps: steps
      });
  
      if (buffer && buffer.geometry) {
        let ptsG = buffer.geometry.coordinates;
        let type = buffer.geometry.type;
  
        let bufferPoints = undefined;
        let holes = [];
  
        if (ptsG && ptsG[0] && type === 'Polygon') {
          bufferPoints = ptsG[0].map(item => {
            return this.cartesianFromDegreeArray(item);
          });
        }
  
        if (ptsG && ptsG.length > 1) {
          for (let i = 1, len = ptsG.length; i < len; ++i) {
            let holePoints = ptsG[i].map(item => {
              return this.cartesianFromDegreeArray(item);
            });
            holes.push(holePoints)
          }
        }
  
        if (bufferPoints) {
          this._bufferPrimitive = this.createBufferPrimitive(bufferPoints, holes);
          this._viewer.scene.primitives.add(this._bufferPrimitive);
        }
      }
  
      if (primitive) {
        this._primitive = this._viewer.scene.primitives.add(primitive);;
      }
  
      this._viewer.scene.requestRender();
    }
  
    onLeftClick(movement) {
      if (this._viewer.scene.currentTool !== this) {
        this._startCapture && this.endEdit();
        return;
      }
  
      if (Freedo.defaultValue(this._data.editDisabled, false) || !this._primitive) {
        return;
      }
  
      if (this._startCapture) {
        this.endEdit();
        return;
      }
  
      if (movement && movement.position) {
        let picked = this._viewer.scene.pick(movement.position);
  
        if (picked && (this._primitive === picked.collection || this._primitive === picked.primitive)) {
          this.startEdit();
        }
      }
    }
  
    onRightClick() {
      this._startCapture && this.endEdit();
    }
  
    onLeftDown(movement) {
      if (this._viewer.scene.currentTool !== this || !this._startCapture) {
        return;
      }
  
      if (!movement || !movement.position) {
        return;
      }
  
      let picked = this._viewer.scene.pick(movement.position);
  
      if (!picked || !picked.id || !this._viewer.entities.contains(picked.id)) {
        return;
      }
  
      this._viewer.container.style.cursor = 'crosshair';
  
      this._actionType = picked.id.actionType;
      this._actionIndex = picked.id.actionIndex;
      this._startCartesian = Freedo.clone(picked.id.position.getValue(), true);
  
      this.setShowEdit(false);
      this._data.type === 'Point' && this.setShowEntity(false);
  
      this._startMove = true;
  
      this.disableCameraController();
      this._viewer.scene.requestRender();
    }
  
    onMouseMove(movement) {
      if (this._viewer.scene.currentTool !== this || !this._startCapture || !this._startMove) {
        return;
      }
  
      if (!movement || !movement.endPosition || movement.endPosition.equals(movement.startPosition)) {
        return;
      }
  
      this._endCartesian = this._viewer.scene.pickPosition(movement.endPosition);
  
      if (!this._endCartesian || !this._startCartesian) {
        return;
      }
  
      let position = Freedo.clone(this._data.position, true);
  
      if (this._data.type === 'Point') {
        // 点只有平移
        let vector = Freedo.Cartesian3.subtract(this._endCartesian, this._startCartesian, new Freedo.Cartesian3());
        let pt = this.cartesianFromDegreeArray(position);
        Freedo.Cartesian3.add(pt, vector, pt);
  
        position = this.degreeArrayFromCartesian(pt);
  
      } else if (this._data.type === 'Polyline' || this._data.type === 'Polygon') {
  
        if (this._actionType === this.ACTION_TYPE.PAN) {
          let vector = Freedo.Cartesian3.subtract(this._endCartesian, this._startCartesian, new Freedo.Cartesian3());
    
          position.forEach((item, i) => {
            let pt = this.cartesianFromDegreeArray(item);
            Freedo.Cartesian3.add(pt, vector, pt);
  
            position[i] = this.degreeArrayFromCartesian(pt);
          });
    
        } else if (this._actionType === this.ACTION_TYPE.VERTEX) {
  
          position[this._actionIndex] = this.degreeArrayFromCartesian(this._endCartesian);
  
        } else if (this._actionType === this.ACTION_TYPE.MIDDLE) {
  
          position.splice(this._actionIndex, 0, this.degreeArrayFromCartesian(this._endCartesian));
  
        }
      }
  
      this._updatePosition = Freedo.clone(position, true);
      this.update(position);
    }
  
    onLeftUp(movement) {
      if (this._viewer.scene.currentTool !== this || !this._startCapture || !this._startMove) {
        return;
      }
  
      if (this._updatePosition) {
        this._data.position = Freedo.clone(this._updatePosition, true);
        this._updatePosition = undefined;
      }
  
      this._viewer.container.style.cursor = 'default';
  
      this.removeAll();
      this.build();
  
      this._actionType = undefined;
      this._actionIndex = undefined;
      this._startCartesian = undefined;
      this._endCartesian = undefined;
      this._startMove = false;
  
      this.restoreCameraController();
      this._viewer.scene.requestRender();
    }
  
    /**
     * 开始编辑
     */
    startEdit() {
      if (!this._editPointEntities) {
        this._editPointEntities = [];
      } else {
        this._editPointEntities.length = 0;
      }
  
      let position =  Freedo.clone(this._data.position, true);
  
      if (this._data.type === 'Point') {
        let point = this.cartesianFromDegreeArray(position);
  
        let ent = this.createEditPoint(point, new Freedo.Color(1.0, 0.0, 1.0, 0.8), this.ACTION_TYPE.PAN, 0);
        this._editPointEntities.push(ent);
  
        this.setShowEntity(false);
  
      } else if (this._data.type === 'Polyline' || this._data.type === 'Polygon') {
        let panPosition = [0, 0, 0];
  
        for (let i = 0, len = position.length; i < len; ++i) {
          panPosition[0] += position[i][0] / len;
          panPosition[1] += position[i][1] / len;
          panPosition[2] += position[i][2] / len || 0.0;
  
          let vertexPt = this.cartesianFromDegreeArray(position[i]);
  
          let ent = this.createEditPoint(vertexPt, new Freedo.Color(0.0, 1.0, 1.0, 0.5), this.ACTION_TYPE.VERTEX, i);
          this._editPointEntities.push(ent);
  
          let middlePt = undefined;
  
          if (i + 1 < len || this._data.type === 'Polygon') {
            let nextPt = this.cartesianFromDegreeArray(position[(i + 1) % len]);
            middlePt = Freedo.Cartesian3.midpoint(vertexPt, nextPt, new Freedo.Cartesian3());;
          }
  
          if (middlePt) {
            let ent = this.createEditPoint(middlePt, new Freedo.Color(0.0, 0.0, 1.0, 0.5), this.ACTION_TYPE.MIDDLE, i + 1);
            this._editPointEntities.push(ent);
          }
        }
  
        let panPt = this.cartesianFromDegreeArray(panPosition);
        let ent = this.createEditPoint(panPt, new Freedo.Color(1.0, 0.0, 1.0, 0.8), this.ACTION_TYPE.PAN, 0);
        this._editPointEntities.push(ent);
      }
  
      this._viewer.scene.currentTool = this;
      this._startCapture = true;
  
      this._viewer.scene.requestRender();
    }
  
    /**
     * 结束编辑
     */
    endEdit() {
      this.setShowEntity(true);
      this.setShowEdit(false);
      
      this._startCapture = false;
      this._viewer.scene.requestRender();
    }
  
    /**
     * 获取数据
     */
    getData() {
      let result = undefined;
  
      if (this._data) {
        this._data.show = this._show;
        result = {
          id: this._id,
          option: this._data
        }
      }
  
      return result;
    }
    
    /**
     * 移除所有primitive
     */
    removeAll(){
      this.removeEntity();
      this.removeBuffer();
      this.removeEdit();
      
      this._viewer.scene.requestRender();
    }
  
    /**
     * 销毁
     */
    destroy() {
      this.removeAll();
  
      this._handler = this._handler && this._handler.destroy();
      if (this._viewer.scene.currentTool === this) {
        this._viewer.scene.currentTool = undefined;
      }
  
      this._startCapture = false;
      this._actionType = undefined;
      this._actionIndex = undefined;
    }
  
    removeEntity() {
      if (this._primitive) {
        if (this._primitive instanceof Freedo.PointPrimitiveCollection) {
          this._primitive.removeAll();
        }
        this._viewer.scene.primitives.remove(this._primitive);
        this._primitive = undefined;
      }
    }
  
    removeBuffer() {
      if (this._bufferPrimitive) {
        this._viewer.scene.primitives.remove(this._bufferPrimitive);
        this._bufferPrimitive = undefined;
      }
    }
  
    removeEdit() {
      if (this._editPointEntities) {
        for (let i = 0, len = this._editPointEntities.length; i < len; ++i) {
          let ent = this._editPointEntities[i];
          this._viewer.entities.remove(ent);
        }
        this._editPointEntities = undefined;
      }
    }
  
    setShowEntity(show) {
      if (this._primitive) {
        if (this._primitive instanceof Freedo.PointPrimitiveCollection) {
          for (let i = 0, len = this._primitive.length; i < len; ++i) {
            let pmt = this._primitive.get(i);
            if (pmt) {
              pmt.show = show;
            } 
          }
        } else {
          this._primitive.show = show;
        }
      }
    }
  
    setShowBuffer(show) {
      if (this._bufferPrimitive) {
        this._bufferPrimitive.show = show;
      }
    }
  
    setShowEdit(show) {
      if (this._editPointEntities) {
        for (let i = 0, len = this._editPointEntities.length; i < len; ++i) {
          let ent = this._editPointEntities[i];
          ent.show = show;
        }
      }
    }
  
    cartesianFromDegreeArray(position) {
      let lon = position[0];
      let lat = position[1];
      let alt = position[2] || 0.0;
      return new Freedo.Cartesian3.fromDegrees(lon, lat, alt);
    }
  
    degreeArrayFromCartesian(cartesian) {
      let rad = Freedo.Cartographic.fromCartesian(cartesian);
      let lon = Freedo.Math.toDegrees(rad.longitude);
      let lat = Freedo.Math.toDegrees(rad.latitude);
      return [lon, lat, rad.height];
    }
  
    /**
     * @private
     * @param {Cartesian3} position
     * @param {Color} color 
     * @param {Number} type actionType
     * @param {Number} index actionIndex
     */
    createEditPoint(position, color, type, index) {
      let ent = this._viewer.entities.add({
        position: position,
        id: Freedo.createGuid(),
        point: {
          heightReference: Freedo.HeightReference.CLAMP_TO_GROUND,
          pixelSize: 10.0,
          color: color,
          outlineColor: new Freedo.Color(1.0, 1.0, 1.0, 0.5),
          outlineWidth: 2.0,
          disableDepthTestDistance: Number.POSITIVE_INFINITY
        },
        show: true,
      }); 
      ent.actionType = type;
      ent.actionIndex = index;
      return ent;
    }
  
    createPoint(position) {
      let pmt = new Freedo.PointPrimitiveCollection({
        allowPicking: true,
        releaseMaterials: true
      });
  
      let pixelSize = Freedo.defaultValue(this._data.pixelSize, 12.0);
      let color = Freedo.defaultValue(this._data.color, new Freedo.Color(1.0, 1.0, 0.0, 1.0));
      let outlineColor = Freedo.defaultValue(this._data.outlineColor, new Freedo.Color(1.0, 0.5, 0.0, 1.0));
      let outlineWidth = Freedo.defaultValue(this._data.outlineWidth, 0.0);
  
      pmt.add({
        id: Freedo.createGuid(),
        position: position,
        pixelSize: pixelSize,
        color: color,
        outlineColor: outlineColor,
        outlineWidth: outlineWidth,
        disableDepthTestDistance: Number.POSITIVE_INFINITY
      });
  
      return pmt;
    }
  
    createPolyline(points) {
      let width = Freedo.defaultValue(this._data.lineWidth, 4.0);
      let lineColor = Freedo.defaultValue(this._data.lineColor, new Freedo.Color(1.0, 1.0, 0.0, 1.0));
  
      return new Freedo.GroundPolylinePrimitive({
        geometryInstances: new Freedo.GeometryInstance({
          id: Freedo.createGuid(),
          geometry: new Freedo.GroundPolylineGeometry({
            positions: points,
            width: width
          })
        }),
        appearance: new Freedo.PolylineMaterialAppearance({
          flat: true,
          material: Freedo.Material.fromType('Color', { color: lineColor })
        }),
        allowPicking: true,
        asynchronous: false,
        releaseMaterials: true
      });
    }
  
    createPolygon(points) {
      let color = Freedo.defaultValue(this._data.color, new Freedo.Color(1.0, 1.0, 0.0, 1.0));
  
      return new Freedo.GroundPrimitive({
        geometryInstances: new Freedo.GeometryInstance({
          id: Freedo.createGuid(),
          geometry: new Freedo.PolygonGeometry({
            polygonHierarchy: new Freedo.PolygonHierarchy(points)
          })
        }),
        appearance: new Freedo.MaterialAppearance({
          flat: true,
          material: Freedo.Material.fromType('Color', {
            color: color
          })
        }),
        allowPicking: true,
        asynchronous: false,
        releaseMaterials: true
      });
    }
  
    createBufferPrimitive(points, holes) {
      let hierarchyHole = undefined;
      if (holes && holes.length) {
        hierarchyHole = holes.map(item => {
          return new Freedo.PolygonHierarchy(item);
        });
      }
      let color = Freedo.defaultValue(this._data.bufferColor, new Freedo.Color(1.0, 0.0, 0.0, 0.5));
  
      return new Freedo.GroundPrimitive({
        geometryInstances: new Freedo.GeometryInstance({
          id: 'buffer-polygon_' + this._id,
          geometry: new Freedo.PolygonGeometry({
            polygonHierarchy: new Freedo.PolygonHierarchy(points, hierarchyHole)
          })
        }),
        appearance: new Freedo.MaterialAppearance({
          flat: true,
          material: Freedo.Material.fromType('Color', {
            color: color
          })
        }),
        allowPicking: false,
        asynchronous: false,
        releaseMaterials: true
      });
    }
  
    storeCameraController() {
      if (!this._cameraController) {
        let controller = this._viewer.scene.screenSpaceCameraController;
        this._cameraController = {
          enableInputs: controller.enableInputs,
          enableRotate: controller.enableRotate,
          enableTranslate: controller.enableTranslate,
          enableZoom: controller.enableZoom,
          enableTilt: controller.enableTilt,
          enableLook: controller.enableLook,
        };
      }
    }
  
    disableCameraController() {
      if (this._cameraController) {
        for (let key in this._cameraController) {
          this._viewer.scene.screenSpaceCameraController[key] = false;
        }
      } else {
        this.storeCameraController();
        this.disableCameraController();
      }
    }
  
    restoreCameraController() {
      // 清除移动行为
      this._viewer.scene.screenSpaceCameraController.update();
  
      // 清除惯性行为
      let movement = this._viewer.scene.screenSpaceCameraController._aggregator._lastMovement;
      for (let key in movement) {
        movement[key].valid = false;
      }
  
      if (this._cameraController) {
        for (let key in this._cameraController) {
          this._viewer.scene.screenSpaceCameraController[key] = this._cameraController[key];
        }
        this._cameraController = undefined;
      }
    }
  }

 
window.Buffer=Buffer